﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using UploadClient.Interfaces;
using UploadClient.Interfaces.Upload;

namespace FtpUploadPlugin
{
    /// <summary>
    /// Upload inherit from BackgroundWorker and manage the uploadprocess.
    /// </summary>
    class Upload : BackgroundWorker
    {
        /// <summary>
        /// Adds the DoWork-Event internal.
        /// </summary>
        public Upload()
        {
            this.DoWork += new DoWorkEventHandler(Upload_DoWork);
        }

        public IUploadHost Host { private get; set; }

        /// <summary>
        /// The work that runs in the background thread.
        /// <remarks>All UploadItems need the exact same ServerSettings!</remarks>
        /// </summary>
        /// <param name="sender">BackgroundWorker.</param>
        /// <param name="e">e.Argument from type UploadItem[].</param>
        [DebuggerNonUserCode()]
        private void Upload_DoWork(object sender, DoWorkEventArgs e)
        {
            // params
            BackgroundWorker bw = sender as BackgroundWorker;
            UploadItem[] uploadItems = e.Argument as UploadItem[];
            var settings = uploadItems[0].ServerSettings as IServerSettings;

            // compare host and dir
            string host = settings.Host;
            string dir = settings.Dir;

            host = host.EndsWith("/") ? host.Substring(0, host.Length - 2) : host;
            host = host.ToLower().StartsWith("ftp://") ? host : host = "ftp://" + host;

            dir = dir.StartsWith("/") ? dir : "/" + dir;
            dir = dir.EndsWith("/") ? dir : dir + "/";

            #region ToDo: Open one connection for all uploads!

            //Uri uri = new Uri(host + dir);

            //// create request
            //FtpWebRequest request = (FtpWebRequest)WebRequest.Create(uri);
            //request.UseBinary = true;
            //request.UsePassive = settings.Passive;
            //request.Method = WebRequestMethods.Ftp.UploadFile;
            //request.Credentials = new NetworkCredential(settings.Username, settings.Password);

            // ToDo: implement proxy! 
            // request.Proxy = new WebProxy();

            #endregion

            // params
            long FileSize;
            string FileSizeDescription, SummaryText;
            int ChunkSize = 4096, NumRetries, MaxRetries = 50, finishedFiles = -1;
            long SentBytes;
            byte[] Buffer = new byte[ChunkSize]; // this buffer stores each chunk, for sending to the web service via MTOM


            foreach (UploadItem uploadItem in uploadItems)
            {
                Uri uri = new Uri(string.Concat(host, dir, uploadItem.Filename, uploadItem.Extension));

                // create request
                FtpWebRequest request = (FtpWebRequest)WebRequest.Create(uri);
                request.UseBinary = true;
                request.UsePassive = settings.Passive;
                request.Method = WebRequestMethods.Ftp.UploadFile;
                request.Credentials = new NetworkCredential(settings.Username, settings.Password);

                // open request
                using (Stream requestStream = request.GetRequestStream())
                {

                    finishedFiles++;

                    // cancel upload
                    if (bw.CancellationPending) { e.Cancel = true; return; }

                    // copy the contents of the file to the request stream.
                    FileSize = new FileInfo(uploadItem.Filepath).Length;
                    FileSizeDescription = GetFileSize(FileSize); // e.g. "2.4 Gb" instead of 240000000000000 bytes etc...			
                    NumRetries = 0;
                    SentBytes = 0;

                    using (FileStream fs = File.Open(uploadItem.Filepath, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        int BytesRead = fs.Read(Buffer, 0, ChunkSize);	// read the first chunk in the buffer

                        // send the chunks to the web service one by one, until FileStream.Read() returns 0, meaning the entire file has been read.
                        while (BytesRead > 0)
                        {
                            try
                            {
                                // cancel upload
                                if (bw.CancellationPending) { e.Cancel = true; return; }

                                // send this chunk to the server.  it is sent as a byte[] parameter, but the client and server have been configured to encode byte[] using MTOM. 
                                requestStream.Write(Buffer, 0, BytesRead);

                                // sentBytes is only updated AFTER a successful send of the bytes. so it would be possible to build in 'retry' code, to resume the upload from the current SentBytes position if AppendChunk fails.
                                SentBytes += BytesRead;

                                // return progress
                                if (bw.WorkerReportsProgress)
                                {
                                    UserState us = new UserState();

                                    SummaryText = String.Format(Host.GetLocalizationRessourceString("{0} - Transferred {1} / {2}"), uploadItem.Filename, GetFileSize(SentBytes), FileSizeDescription);
                                    us.Message = SummaryText;
                                    us.GetFinishedFilesCount = finishedFiles;
                                    us.GetRemainingQueCount = uploadItems.Count() - finishedFiles;

                                    bw.ReportProgress((int)(((decimal)SentBytes / (decimal)FileSize) * 100), us);
                                }
                            }
                            catch (Exception ex)
                            {
                                if (NumRetries++ < MaxRetries)
                                {
                                    // rewind the filestream and keep trying
                                    fs.Position -= BytesRead;
                                }
                                // too many retrys
                                else
                                {
                                    Exception exception = new Exception(Host.GetLocalizationRessourceString("Error occurred during upload, too many retries"), ex);
                                    Host.ThrowException(exception);
                                    throw exception;
                                }
                            }

                            // read the next chunk (if it exists) into the buffer.  the while loop will terminate if there is nothing left to read
                            BytesRead = fs.Read(Buffer, 0, ChunkSize);
                        }
                    }
                }
            }

        }

        /// <summary>
        /// Returns a description of a number of bytes, in appropriate units.
        /// e.g. 
        ///		passing in 1024 will return a string "1 Kb"
        ///		passing in 1230000 will return "1.23 Mb"
        /// Megabytes and Gigabytes are formatted to 2 decimal places.
        /// Kilobytes are rounded to whole numbers.
        /// If the rounding results in 0 Kb, "1 Kb" is returned, because Windows behaves like this also.
        /// </summary>
        private string GetFileSize(long numBytes)
        {
            string fileSize = "";

            if (numBytes > 1073741824)
                fileSize = String.Format("{0:0.00} Gb", (double)numBytes / 1073741824);
            else if (numBytes > 1048576)
                fileSize = String.Format("{0:0.00} Mb", (double)numBytes / 1048576);
            else
                fileSize = String.Format("{0:0} Kb", (double)numBytes / 1024);

            if (fileSize == "0 Kb")
                fileSize = "1 Kb";	// min.							
            return fileSize;
        }
    }
}
